package org.ikross.h2;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.sql.SQLException;

import org.h2.message.Message;
import org.ikross.h2.wkb.WKBUtil;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.ParseException;

public class SpatialFunctions {
	private static GeometryFactory geometryFactory;
	private static LineString lineString;
	private static byte[] serializedLineString;
	private static Geometry envelope;
	private static byte[] serializedEnvelope;
	
//	private static Map cache;
	
	static{
		lineString=null;
		serializedLineString=null;
		
		envelope=null;
		serializedEnvelope=null;
	}
	
	public static byte[] Buffer (byte[] serializedGeometry, double distance) throws SQLException{
		try{
			return WKBUtil.serialize(((Geometry)WKBUtil.unserialize(serializedGeometry)).buffer(distance));
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
		
	public static double MaxX(byte[] serializedGeometry) throws SQLException{
		 try {
			return WKBUtil.unserialize(serializedGeometry).getEnvelope().getCoordinates()[2].x;
		} catch (ParseException e) {
			throw Message.convert(e);
		} catch (IOException e) {
			throw Message.convert(e);
		}
	}
	
	public static double MaxY(byte[] serializedGeometry) throws SQLException{
		 try {
			return WKBUtil.unserialize(serializedGeometry).getEnvelope().getCoordinates()[2].y;
		} catch (ParseException e) {
			throw Message.convert(e);
		} catch (IOException e) {
			throw Message.convert(e);
		}
	}
	
	public static double MinX(byte[] serializedGeometry) throws SQLException{
		 try {
			return WKBUtil.unserialize(serializedGeometry).getEnvelope().getCoordinates()[0].x;
		} catch (ParseException e) {
			throw Message.convert(e);
		} catch (IOException e) {
			throw Message.convert(e);
		}
	}
	
	public static double MinY(byte[] serializedGeometry) throws SQLException{
		 try {
			return WKBUtil.unserialize(serializedGeometry).getEnvelope().getCoordinates()[0].y;
		} catch (ParseException e) {
			throw Message.convert(e);
		} catch (IOException e) {
			throw Message.convert(e);
		}
	}
	
	public static boolean Contains (byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return geom1.contains(geom2);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static byte[] ConvexHull(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return WKBUtil.serialize(geom.convexHull());
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static boolean covers(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return geom1.covers(geom2);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static boolean Crosses(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return geom1.crosses(geom2);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}

	public static byte[] Difference(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return WKBUtil.serialize(geom1.difference(geom2));
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static double Distance(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return geom1.distance(geom2);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static double Area(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return geom.getArea();
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static byte[] Centroid(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return WKBUtil.serialize(geom.getCentroid());
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}

	public static byte[] Envelope(byte[] serializedGeometry) throws SQLException{
		//Geometry Envelope(Geometry) es la version OGC de Geometry bbox(Geometry)
		return bbox(serializedGeometry);
	}

	public static byte[] bbox(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return WKBUtil.serialize(geom.getEnvelope());
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static int SRID(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return geom.getSRID();
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw (except);
		}
	}

	public static byte[] Intersection(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return WKBUtil.serialize(geom1.intersection(geom2));
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static boolean Intersects(byte[] serializedGeometry1, byte[] serializedGeometry2) throws SQLException{
		try{
			Geometry geom1=(Geometry)WKBUtil.unserialize(serializedGeometry1);
			Geometry geom2=(Geometry)WKBUtil.unserialize(serializedGeometry2);
			if(geom2==null) throw new SQLException("Parameter geom2 cannot be null");
			return geom1.intersects(geom2);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static String AsText(byte[] serializedGeometry) throws SQLException{
		//String AsText(Geometry) es la versión OGC de String asWKT(Geometry)
		return asWKT(serializedGeometry);
	}
	
	public static String asWKT(byte[] serializedGeometry) throws SQLException{
		try{
			Geometry geom=(Geometry)WKBUtil.unserialize(serializedGeometry);
			return geom.toText();
		}
		catch(IOException except){
			throw Message.convert(except);
		}
		catch(ParseException except){
			throw Message.convert(except);
		}
		catch(NullPointerException except){
			throw Message.convert(except);
		}
	}
	
	public static byte[] asWKB(Geometry geometry) throws SQLException{
		try{
			return WKBUtil.serialize(geometry);
		}
		catch(IOException except){
			throw Message.convert(except);
		}
	}
	
	public static byte[] geometryFromText(String wellKnownText) throws SQLException{
		try{
			return WKBUtil.serialize(WKBUtil.geometryFromText(wellKnownText));
		}
		catch(IOException except){
			throw Message.convert(except);
		} catch (ParseException except) {
			throw Message.convert(except);
		}
	}
	
	public static byte[] createEnvelope(double xMin, double yMin, double xMax, double yMax, int SRID) throws SQLException{
		try{
			if(geometryFactory==null || geometryFactory.getSRID()!=SRID){
				geometryFactory=new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), SRID);
			}
			
			if(lineString==null || serializedLineString==null || SRID!=lineString.getSRID() || (lineString.getCoordinates()[0].x!=xMin || lineString.getCoordinates()[0].y!=yMin || lineString.getCoordinates()[1].x!=xMax || lineString.getCoordinates()[2].y!=yMax)){
				lineString=geometryFactory.createLineString(new Coordinate[]{
						new Coordinate(xMin, yMin),
						new Coordinate(xMax, yMin),
						new Coordinate(xMax, yMax),
						new Coordinate(xMin, yMax)
				});
				serializedLineString=WKBUtil.serialize(lineString);
			}
			
			if(envelope==null || serializedEnvelope==null || SRID!=envelope.getSRID()){
				envelope=lineString.getEnvelope();
				serializedEnvelope=WKBUtil.serialize(envelope);
			}

			return serializedEnvelope;
		} catch (Exception except) {
			// TODO Auto-generated catch block
			throw Message.convert(except);
		}		
	}
	
	public static byte[] createLineString(double xMin, double yMin, double xMax, double yMax, int SRID) throws SQLException{
		//double box[] = { xMin, yMin, xMax, yMax };
		
//		String sbox=String.valueOf(xMin)+String.valueOf(yMin)+String.valueOf(xMax)+String.valueOf(yMax);
//		byte bytes[];
//		if((bytes=(byte[])cache.get(sbox))!=null ) {
//			return bytes;
//		}
		
		if(geometryFactory==null || geometryFactory.getSRID()!=SRID){
			geometryFactory=new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), SRID);
		}
		
		try {
			if(lineString!=null && SRID==geometryFactory.getSRID()){
				try{
					Coordinate[] coordinates=lineString.getCoordinates();
					if(coordinates[0].x == xMin &&
					   coordinates[0].y == yMin &&
					   coordinates[1].x == xMax &&
					   coordinates[2].y == yMax){
//					    cache.put(sbox,bytes);						
						if(serializedLineString==null)
							serializedLineString=WKBUtil.serialize(lineString);
					}
				}
				catch(IndexOutOfBoundsException except){
					throw Message.convert(except);
				}
				catch(IOException except){
					throw Message.convert(except);
				}
			}
			else{
				lineString=geometryFactory.createLineString(new Coordinate[]{
						new Coordinate(xMin, yMin),
						new Coordinate(xMax, yMin),
						new Coordinate(xMax, yMax),
						new Coordinate(xMin, yMax)
				});
				serializedLineString =WKBUtil.serialize(lineString);
	//		    cache.put(sbox,bytes);
			}

			return serializedLineString;
		} catch (IOException except) {
			// TODO Auto-generated catch block
			throw Message.convert(except);
		}
	}
}
